package fr.vod.threads;

import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import fr.vod.ihm.videoshareucp.MainActivity;
import fr.vod.net.Receiver;
import fr.vod.net.ReceiverMulticast;
import fr.vod.net.Sender;
import fr.vod.net.SenderMulticast;
import fr.vod.util.ExchangeProperties;
import fr.vod.util.LogHelper;
import fr.vod.videoshareucp.R;

public class ServiceMulticastHeartbeat extends IntentService {

	/**
	 * TODO : Eventuellement creer une fonction permettant de parser les actions
	 * en fonctions du paquet (ou du message ?)
	 */

	// / tag des logs
	private final static String logTag = ServiceMulticastHeartbeat.class
			.getName();

	// / Instance "attrapant" les messages entrant
	Messenger messenger = new Messenger(new IncomingHandler());
	public Messenger activityMessenger;

	private boolean isBinded = false;
	private boolean isServer = false;

	private Thread receivePacketThread;
	private Receiver receiver;
	private Sender sender;

	// Variables permettant de gérer les listes de clients (serveurs)
	private String myAddress = null;
	
	private ArrayList<String> interlocutor = new ArrayList<String>();
	private HashMap<String, Boolean> availableDevices = new HashMap<String, Boolean>();

	private boolean displayLog = false;

	public ServiceMulticastHeartbeat() {
		super("ServiceMultiCast");
	}

	@Override
	public IBinder onBind(Intent intent) {
		LogHelper.entree(logTag, "onBind");
		IBinder bind = messenger.getBinder();
		isBinded = true;
		return bind;
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		LogHelper.entree(logTag, "onConfigurationChanged");
		super.onConfigurationChanged(newConfig);
	}

	@Override
	public void onRebind(Intent intent) {
		LogHelper.entree(logTag, "onRebind");
		super.onRebind(intent);
	}
	
	@Override
	public void onCreate() {
		LogHelper.entree(logTag, "onCreate");
		super.onCreate();
	}

	private void prepareServiceForMulticast() {
		myAddress = getLocalIpAddress();
		LogHelper.information(logTag, "myAddress = " + myAddress);
		String ipHeartbeat = getString(R.string.ip_heartbeat);
		LogHelper.information(logTag, "Connecting to " + ipHeartbeat);

		try {
			WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
			MulticastLock mLock = wifi.createMulticastLock("mylock");
			mLock.acquire();
			receiver = new ReceiverMulticast(
					InetAddress.getByName(ipHeartbeat),
					Integer.parseInt(getString(R.string.port_heartbeat)));
			sender = new SenderMulticast(InetAddress.getByName(ipHeartbeat),
					Integer.parseInt(getString(R.string.port_heartbeat)));
		} catch (Exception e) {
			LogHelper.erreur(logTag, e.getMessage());
		}

	}
	
	@Override
	protected void onHandleIntent(Intent workIntent) {
		
		Bundle bundle = workIntent.getExtras();
		isServer = bundle.getBoolean("isServer");
		displayLog = bundle.getBoolean("displayLog");
		
		// On prepare le service en creant les sockets
		prepareServiceForMulticast();
		
		// On creer le thread d'écoute
		receivePacketThread = new ReceivePacketThread();
		receivePacketThread.start();
		
		// Puis on envoi un ping toutes les 30 secondes
		long initTimestamp = System.currentTimeMillis();
		//long endTimestamp;
		int duration;
		
		LogHelper.information(logTag, "preparing multicast thread with isServer = " + isServer);
		String type = null;
		if (isServer)
			type = "SERVER";
		else
			type = "ClIENT";
		
		int interlocutorPreviousSize = 0;
		while (true) {
			//long endTimestamp = System.currentTimeMillis();
			//duration = (int) (endTimestamp / 1000 - initTimestamp / 1000);
			
			// Si le temps ecoule depuis le dernier PING est depasser, on en renvoi un
			if (isBinded == true) {
				if (interlocutorPreviousSize != interlocutor.size()) {
					updateAvailableDevicesList(interlocutor);
					//sendNumberDevicesUpdateToTask(interlocutor.size());
					sendAvailableDevices();
				} 
				
				//initTimestamp = System.currentTimeMillis();
				
				interlocutorPreviousSize = interlocutor.size();
				interlocutor = new ArrayList<String>();
				
				
				try {
					for (int i = 0; i < ExchangeProperties.NUMBER_PING; i++) {
						sender.send(type + " " + "PING");
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				try {
					Thread.sleep(ExchangeProperties.HEARTBEAT_REFRESH);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
		
		/*
			// Puis (s'il le faut) on envoi le nombre de devices atteignables

				// On envoi un message à l'AsyncTask quand il y a moins
				// d'interlocuteurs qu'avant
				if (interlocutor.size() > 0 && interlocutorPreviousSize != interlocutor.size()) {
					sendNumberDevicesUpdateToTask(interlocutor.size();
				}
		 */

	}
	
	// Send Pong avec Update UI
	private void sendPong(String message, String hostAddress) {
		try {
			for (int i = 0; i < ExchangeProperties.NUMBER_PING; i++)
				sender.send(message);

		} catch (Exception e) {
			sendUiUpdateToTask("\tUnable to send PONG");
		}
	}
	
	/*private void sendNumberDevicesUpdateToTask(int size) {
		Message msgNumberDevices = Message.obtain(null,
				ExchangeProperties.MESSAGE_NUMBER_DEVICES, 0, 0);
		Bundle bundleNumberDevices = msgNumberDevices.getData();

		bundleNumberDevices.putInt("numberPonger",
				size);
		LogHelper.information(logTag,
				"SENDING MESSAGE NUMBER DEVICES");
		try {
			activityMessenger.send(msgNumberDevices);
		} catch (RemoteException e) {
			LogHelper.erreur(logTag, e.getMessage());
		}
	}*/

	
	// Send message to append to UI
	private void sendUiUpdateToTask(String stringToSend) {
		if (!isBinded)
			return;
		/*if (!displayLog)
			return;*/
		
		// On fait update l'ui 
		Message msgToSend = Message.obtain(null,
				ExchangeProperties.MESSAGE_UPDATE_UI, 0, 0);
		Bundle bundleNumberDevices = msgToSend.getData();

		bundleNumberDevices.putString("message", stringToSend);
		try {
			activityMessenger.send(msgToSend);
		} catch (RemoteException e) {
			LogHelper.erreur(logTag, e.getMessage());
		}
	}

	
	private void updateAvailableDevicesList(ArrayList<String> interlocutor) {

		/*// On commence par parcourir la liste pour mettre tout à false
		for (Entry<String, Boolean> entry : availableDevices.entrySet()) {
			entry.setValue(false);
		}*/

		// On parcourt la liste des devices ayant repondu un PONG
		for (int i = 0; i < interlocutor.size(); i++) {
			// Si le device n'est pas dans la liste des available devices, on
			// l'ajoute
			if (availableDevices.get(interlocutor.get(i)) == null) {
				availableDevices.put(interlocutor.get(i), true);
			} else {
				if (availableDevices.get(interlocutor.get(i)) == false) {
					availableDevices.remove(interlocutor.get(i));
					availableDevices.put(interlocutor.get(i), true);
				}
			}
		}
	}

	// Send the availableDevices list to the task
	private void sendAvailableDevices() {
		if (!isBinded)
			return;
		
		// On envoi juste les availableDevices
		Message msgAvailableDevices = Message.obtain(null,
				ExchangeProperties.MESSAGE_AVAILABLE_DEVICES, 0, 0);
		Bundle bundleActivity = msgAvailableDevices.getData();
		bundleActivity.putSerializable("availableDevices", availableDevices);
		// LogHelper.information(logTag, "SENDING AVAILABLE DEVICES");

		try {
			activityMessenger.send(msgAvailableDevices);
		} catch (RemoteException e) {
			LogHelper.erreur(logTag, e.getMessage());
		}
	}

	private String getLocalIpAddress() {
		WifiManager wifiManager = (WifiManager) getApplicationContext()
				.getSystemService(Context.WIFI_SERVICE);
		int myIp = wifiManager.getConnectionInfo().getIpAddress();

		int intMyIp3 = myIp / 0x1000000;
		int intMyIp3mod = myIp % 0x1000000;
		int intMyIp2 = intMyIp3mod / 0x10000;
		int intMyIp2mod = intMyIp3mod % 0x10000;
		int intMyIp1 = intMyIp2mod / 0x100;
		int intMyIp0 = intMyIp2mod % 0x100;
		String s = String.valueOf(intMyIp0) + "." + String.valueOf(intMyIp1)
				+ "." + String.valueOf(intMyIp2) + "."
				+ String.valueOf(intMyIp3);
		boolean chaineOK = true;
		for (int i = 0; i < s.length(); i++)
			if (s.charAt(i) != '.' && !Character.isDigit(s.charAt(i)))
				chaineOK = false;
		if (!chaineOK)
			s = null;

		return s;
	}

	/**
	 * Handler des messages entrant.
	 * 
	 * repond a l'emetteur avec le flag pour savoir si le bluetooth est active
	 * ou pas
	 * 
	 */
	private class IncomingHandler extends Handler {

		@Override
		public void handleMessage(Message msg) {
			// LogHelper.entree(logTag, "handleMessage");

			switch (msg.what) {
			case ExchangeProperties.MESSAGE_HELLO:
				// LogHelper.information(logTag, "MESSAGE HELLO received");
				activityMessenger = msg.replyTo;

				// On receptionne le bundle indiquant le type de device (serveur
				// ou client)
				Bundle receivedBundle = msg.getData();
				isServer = receivedBundle.getBoolean("isServer");
				 //LogHelper.information(logTag, "isServer received : " +
				 //isServer);

				// Puis on repond à l'AsyncTask
				Message msgToTask = Message.obtain(this,
						ExchangeProperties.MESSAGE_HELLO, 0, 0);
				Bundle bundleToSend = msgToTask.getData();

				bundleToSend.putString("flag", "Bonjour AsyncTask");
				bundleToSend.putBoolean("isServer", isServer);
				try {
					activityMessenger.send(msgToTask);
				} catch (RemoteException e) {
					LogHelper.erreur(logTag, e.getMessage());
				}

				break;
			case ExchangeProperties.MESSAGE_NUMBER_DEVICES:
			case ExchangeProperties.MESSAGE_AVAILABLE_DEVICES:
				break;
			default:
				super.handleMessage(msg);
				break;
			}
		}
	}

	private class ReceivePacketThread extends Thread {
		
		public void run() {
			DatagramPacket packet = null;
			
			while (true) {
				// Ici on recoit les packets
				try {
					packet = receiver.receivePacket();
					//long startHandlingPacketTimer = System.currentTimeMillis();
					parsePacket(packet);
					//if (!packet.getAddress().getHostAddress().contains(myAddress))
						//LogHelper.information(logTag, "Packet parsed in  : " + (new Date().getTime()/1000 - startHandlingPacketTimer/1000) + " s");
					
				} catch (Exception e) {
					LogHelper.erreur(logTag, "Error in receiving thread");
				}
			}
			
		}
		
		private boolean parsePacket(DatagramPacket packet) throws UnsupportedEncodingException {
			
			String ipPacketSender = packet.getAddress().getHostAddress();
			// Si le sender est lui-même, on ne traite pas
			if (ipPacketSender.contains(myAddress))
				return false;
			
			String receivedString = new String(packet.getData(), "UTF-8").trim();
			sendUiUpdateToTask("--> " + receivedString + " @ " + ipPacketSender);
			
			if (receivedString.contains("PING")) {
				respondToPing(receivedString, ipPacketSender);
				return true;
			}
			
			boolean isNew = false;
			if (receivedString.contains("PONG"))
				isNew = handlePongMessage(ipPacketSender, receivedString);
			
			if (isNew) {
				sendUiUpdateToTask("\t New device detected : " + ipPacketSender);
				updateAvailableDevicesList(interlocutor);
			}
			
			return isNew;
		}

		/**
		 * Permet de vérifier si le device est connu (s'il apparait déjà dans interlocutor)
		 * Pour cela, utilise la méthode verifyAndAddIp
		 * 
		 * @param ipPacketSender
		 * @param receivedString
		 * @return false si l'interlocuteur est déjà connu
		 * 
		 */
		private boolean handlePongMessage(String ipPacketSender, String receivedString) {
			
			if (isServer) {
				// Si on est un serveur, on recherche les clients
				if (receivedString.contains("SERVER")) {
					return false;
				} else {
					// On appel alreadyKnown qui verifie si le device est connu
					// Si inconnu, l'ajoute et retourne false. Si connu, retourne true
					if (verifyAndAddIp(ipPacketSender) && ipPacketSender != null) {
						//LogHelper.information(logTag, "alreadyKnown " + ipPacketSender);
						return false;
					}
				}
			} else {
				if (receivedString.contains("CLIENT")) {
					return false;
				} else {
					// On appel alreadyKnown qui verifie si le device est connu
					// Si inconnu, l'ajoute et retourne false. Si connu,retourne true
					if (verifyAndAddIp(ipPacketSender) && ipPacketSender != null) {
						//LogHelper.information(logTag, "alreadyKnown " + ipPacketSender);
						return false;
					}
				}
			}
			
			return true;
		}
		
		/**
		 * 
		 * Si le serveur n'est pas encore dans interlocutor, le rajoute dans interlocutor, puis renvoie false.
		 * 
		 * @param serverAddress
		 * @return true si l'adresse ip est déjà connue, sinon on l'ajoute dans interlocutor et on retourne false
		 */
		private boolean verifyAndAddIp(String serverAddress) {
			// On parcourt la liste c'est elle n'est pas vide
			if (interlocutor.size() > 0) {
				for (int i = 0; i < interlocutor.size(); i++) {
					// Si on le trouve, on retourne true
					if (serverAddress.equals(interlocutor.get(i)))
						return true;
					else
						continue;
				}
			}

			// Sinon on peut l'ajouter
			interlocutor.add(serverAddress);
			return false;
		}	

		private void respondToPing(String receivedString, String hostAddress) {
			if (isServer) {
				if (receivedString.contains("SERVER")) {
					return;
				} else {
					sendPong("SERVER" + " " + "PONG", hostAddress);
				}
			} else {
				if (receivedString.contains("CLIENT")) {
					return;
				} else {
					sendPong("CLIENT" + " " + "PONG", hostAddress);
				}

			}
			
		}

	}

}